/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "platform/ohos/rs_modifiers_draw.h"

#include "command/rs_canvas_node_command.h"
#include "command/rs_command.h"
#include "command/rs_node_command.h"
#include "pipeline/rs_modifiers_draw_thread.h"
#include "render_context/memory_handler.h"

#include "ffrt_inner.h"
#include "qos.h"

namespace OHOS {
namespace Rosen {
std::mutex RSModifiersDraw::surfaceMutex_;
std::unordered_map<NodeId, RSModifiersDraw::SurfaceAndPixelMap> RSModifiersDraw::surfaceMap_;

sptr<SurfaceBuffer> RSModifiersDraw::DmaMemAlloc(
    int32_t width, int32_t height, const std::unique_ptr<Media::PixelMap>& pixelMap)
{
    sptr<SurfaceBuffer> surfaceBuffer = SurfaceBuffer::Create();
    if (!surfaceBuffer) {
        RS_LOGE("%{public}s SurfaceBuffer SurfaceBuffer::Create failed!", __func__);
        return nullptr;
    }
    BufferRequestConfig requestConfig = {
        .width = width,
        .height = height,
        .strideAlignment = 0x8,                // set 0x8 as default value to alloc SurfaceBufferImpl
        .format = GRAPHIC_PIXEL_FMT_RGBA_8888, // PixelFormat
        .usage = BUFFER_USAGE_CPU_READ | BUFFER_USAGE_HW_RENDER | BUFFER_USAGE_MEM_MMZ_CACHE | BUFFER_USAGE_MEM_DMA,
        .timeout = 0,
        .colorGamut = GraphicColorGamut::GRAPHIC_COLOR_GAMUT_SRGB,
        .transform = GraphicTransformType::GRAPHIC_ROTATE_NONE,
    };
    GSError ret = surfaceBuffer->Alloc(requestConfig);
    if (ret != GSERROR_OK) {
        RS_LOGE("%{public}s SurfaceBuffer Alloc failed, %{public}s", __func__, GSErrorStr(ret).c_str());
        return nullptr;
    }
    void* nativeBuffer = surfaceBuffer.GetRefPtr();
    OHOS::RefBase* ref = reinterpret_cast<OHOS::RefBase*>(nativeBuffer);
    ref->IncStrongRef(ref);
    int32_t bufferSize = pixelMap->GetByteCount();
    pixelMap->SetPixelsAddr(
        surfaceBuffer->GetVirAddr(), nativeBuffer, bufferSize, Media::AllocatorType::DMA_ALLOC, nullptr);
    return surfaceBuffer;
}

std::shared_ptr<Drawing::Surface> RSModifiersDraw::CreateSurfaceFromGpuContext(
    const std::unique_ptr<Media::PixelMap>& pixelMap, int32_t width, int32_t height)
{
    sptr<SurfaceBuffer> surfaceBufferTmp = DmaMemAlloc(width, height, pixelMap);
    if (!surfaceBufferTmp) {
        RS_LOGE("%{public}s DmaMemAlloc fail.", __func__);
        return nullptr;
    }

    OHNativeWindowBuffer* nativeWindowBufferTmp = CreateNativeWindowBufferFromSurfaceBuffer(&surfaceBufferTmp);
    if (!nativeWindowBufferTmp) {
        RS_LOGE("%{public}s CreateNativeWindowBufferFromSurfaceBuffer fail", __func__);
        return nullptr;
    }

    Drawing::BackendTexture backendTextureTmp = NativeBufferUtils::MakeBackendTextureFromNativeBufferClient(
        nativeWindowBufferTmp, surfaceBufferTmp->GetWidth(), surfaceBufferTmp->GetHeight());
    if (!backendTextureTmp.IsValid()) {
        DestroyNativeWindowBuffer(nativeWindowBufferTmp);
        RS_LOGE("%{public}s MakeBackendTextureFromNativeBuffer fail", __func__);
        return nullptr;
    }

    auto vkTextureInfo = backendTextureTmp.GetTextureInfo().GetVKTextureInfo();
    vkTextureInfo->imageUsageFlags = vkTextureInfo->imageUsageFlags | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    auto cleanUpHelper = new NativeBufferUtils::VulkanCleanupHelperClient(
        RsVulkanContextClient::GetSingleton(), vkTextureInfo->vkImage, vkTextureInfo->vkAlloc.memory);
    if (!cleanUpHelper) {
        DestroyNativeWindowBuffer(nativeWindowBufferTmp);
        RS_LOGE("%{public}s Construct VulkanCleanupHelper fail.", __func__);
        return nullptr;
    }
    auto gpuContext = RsVulkanContextClient::GetSingleton().CreateDrawingContext();
    if (!gpuContext) {
        RS_LOGE("%{public}s CreateDrawingContext fail.", __func__);
        delete cleanUpHelper;
        cleanUpHelper = nullptr;
        DestroyNativeWindowBuffer(nativeWindowBufferTmp);
        return nullptr;
    }

    Drawing::GPUContextOptions options;
    auto handler = std::make_shared<MemoryHandler>();
    std::string vulkanVersion = RsVulkanContextClient::GetSingleton().GetVulkanVersion();
    handler->ConfigureContext(&options, vulkanVersion.c_str(), vulkanVersion.size(),
        RSModifiersDrawThread::Instance().GetCacheDir(), false);
    std::shared_ptr<Drawing::Surface> drawingSurface = Drawing::Surface::MakeFromBackendTexture(gpuContext.get(),
        backendTextureTmp.GetTextureInfo(), Drawing::TextureOrigin::TOP_LEFT, 1,
        Drawing::ColorType::COLORTYPE_RGBA_8888, Drawing::ColorSpace::CreateSRGB(),
        NativeBufferUtils::DeleteVkImageClient, cleanUpHelper);
    if (!drawingSurface) {
        RS_LOGE("%{public}s MakeFromBackendTexture fail.", __func__);
    }
    DestroyNativeWindowBuffer(nativeWindowBufferTmp);
    return drawingSurface;
}

std::shared_ptr<Drawing::Surface> RSModifiersDraw::CreateSurfaceFromCpuContext(
    const std::unique_ptr<Media::PixelMap>& pixelMap)
{
    auto address = const_cast<uint32_t*>(pixelMap->GetPixel32(0, 0));
    if (address == nullptr) {
        RS_LOGE("%{public}s PixelMap address null.", __func__);
        return nullptr;
    }
    Drawing::ImageInfo info =
        Drawing::ImageInfo { pixelMap->GetWidth(), pixelMap->GetHeight(), Drawing::ColorType::COLORTYPE_RGBA_8888,
            Drawing::AlphaType::ALPHATYPE_PREMUL, Drawing::ColorSpace::CreateSRGB() };
    auto surface = Drawing::Surface::MakeRasterDirect(info, address, pixelMap->GetRowBytes());
    if (surface == nullptr) {
        RS_LOGE("%{public}s Create surface by MakeRasterDirect fail.", __func__);
    }
    return surface;
}

std::shared_ptr<Drawing::Surface> RSModifiersDraw::CreateSurface(std::unique_ptr<Media::PixelMap>& pixelMap,
    int32_t width, int32_t height)
{
    auto surface = CreateSurfaceFromGpuContext(pixelMap, width, height);
    if (surface == nullptr) {
        RS_LOGW("%{public}s Create surface from cpu context, width=%{public}d, height=%{public}d",
            __func__, width, height);
        pixelMap = CreatePixelMap(width, height);
        if (pixelMap == nullptr) {
            RS_LOGE("%{public}s Create pixelMap fail.", __func__);
            return nullptr;
        }
        surface = CreateSurfaceFromCpuContext(pixelMap);
    }
    return surface;
}

std::unique_ptr<Media::PixelMap> RSModifiersDraw::CreatePixelMap(int32_t width, int32_t height)
{
    Media::InitializationOptions options;
    options.size.width = width;
    options.size.height = height;
    options.srcPixelFormat = Media::PixelFormat::RGBA_8888;
    options.pixelFormat = Media::PixelFormat::RGBA_8888;
    options.alphaType = Media::AlphaType::IMAGE_ALPHA_TYPE_PREMUL;
    // If one of the width and height is 0, PixelMap::Create return nullptr.
    return Media::PixelMap::Create(options);
}

RSModifiersDraw::SurfaceAndPixelMap RSModifiersDraw::GetSurfaceAndPixelMapByNodeId(NodeId nodeId)
{
    std::lock_guard<std::mutex> lock(surfaceMutex_);
    auto it = surfaceMap_.find(nodeId);
    if (it != surfaceMap_.end()) {
        return it->second;
    }
    return std::make_pair(nullptr, nullptr);
}

void RSModifiersDraw::Playback(const std::shared_ptr<Drawing::Surface>& surface,
    const std::shared_ptr<Drawing::DrawCmdList>& cmdList, bool isCanvasType)
{
    auto canvas = surface->GetCanvas();
    cmdList->Playback(*canvas);
    if (RSSystemProperties::GetHybridRenderDfxEnabled()) {
        auto matrix = canvas->GetTotalMatrix();
        auto scaleX = matrix.Get(Drawing::Matrix::Index::SCALE_X);
        auto scaleY = matrix.Get(Drawing::Matrix::Index::SCALE_Y);
        matrix.Set(Drawing::Matrix::Index::SCALE_X, 1.f);
        matrix.Set(Drawing::Matrix::Index::SCALE_Y, 1.f);
        canvas->SetMatrix(matrix);
        Drawing::Pen pen;
        pen.SetWidth(10); // DFX border width 10
        pen.SetColor(isCanvasType ? 0xFFFF8000 : Drawing::Color::COLOR_BLUE); // 0xFFFF8000 is orange
        canvas->AttachPen(pen);
        auto rect = Drawing::Rect(0, 0, cmdList->GetWidth(), cmdList->GetHeight());
        canvas->DrawRect(rect);
        canvas->DetachPen();
        matrix.Set(Drawing::Matrix::Index::SCALE_X, scaleX);
        matrix.Set(Drawing::Matrix::Index::SCALE_Y, scaleY);
        canvas->SetMatrix(matrix);
    }
    surface->FlushAndSubmit(isCanvasType);
}

void RSModifiersDraw::AddPixelMapDrawOp(const std::shared_ptr<Drawing::DrawCmdList>& cmdList,
    const std::shared_ptr<Media::PixelMap>& pixelMap, int32_t width, int32_t height)
{
    Drawing::AdaptiveImageInfo adaptiveImageInfo = { 0, 0, {}, 1.0, 0, width, height };
    auto object = std::make_shared<RSExtendImageObject>(pixelMap, adaptiveImageInfo);
    auto objectHandle = Drawing::CmdListHelper::AddImageObjectToCmdList(*cmdList, object);
    Drawing::PaintHandle paintHandle;
    paintHandle.isAntiAlias = true;
    paintHandle.blenderEnabled = true;
    paintHandle.style = Drawing::Paint::PaintStyle::PAINT_FILL;
    Drawing::SamplingOptions sampling(Drawing::FilterMode::LINEAR, Drawing::MipmapMode::LINEAR);
    cmdList->AddDrawOp<Drawing::DrawPixelMapWithParmOpItem::ConstructorHandle>(objectHandle, sampling, paintHandle);
}

void RSModifiersDraw::InvalidateSurfaceCache(const std::shared_ptr<Media::PixelMap>& pixelMap)
{
    if (pixelMap == nullptr || pixelMap->GetAllocatorType() != Media::AllocatorType::DMA_ALLOC) {
        RS_LOGE("%{public}s PixelMap null or not DMA", __func__);
        return;
    }
    SurfaceBuffer* surfaceBuffer = reinterpret_cast<SurfaceBuffer*>(pixelMap->GetFd());
    if (surfaceBuffer == nullptr || !(surfaceBuffer->GetUsage() & BUFFER_USAGE_MEM_MMZ_CACHE)) {
        RS_LOGE("%{public}s SurfaceBuffer null or no usage BUFFER_USAGE_MEM_MMZ_CACHE", __func__);
        return;
    }
    auto error = surfaceBuffer->Map();
    if (error != GSERROR_OK) {
        RS_LOGE("%{public}s Map failed, error=%{public}d", __func__, error);
        return;
    }
    error = surfaceBuffer->InvalidateCache();
    if (error != GSERROR_OK) {
        RS_LOGE("%{public}s InvalidateCache failed, error=%{public}d", __func__, error);
    }
}

void RSModifiersDraw::ConvertCmdListForCanvas(const std::shared_ptr<Drawing::DrawCmdList>& cmdList, NodeId nodeId)
{
    auto width = cmdList->GetWidth();
    auto height = cmdList->GetHeight();
    auto surfaceAndPixelMap = GetSurfaceAndPixelMapByNodeId(nodeId);
    if (surfaceAndPixelMap.first == nullptr || surfaceAndPixelMap.second == nullptr) {
        ResetSurfaceByNodeId(width, height, nodeId);
        surfaceAndPixelMap = GetSurfaceAndPixelMapByNodeId(nodeId);
    }
    auto surface = surfaceAndPixelMap.first;
    auto pixelMap = surfaceAndPixelMap.second;
    if (surface == nullptr || pixelMap == nullptr) {
        RS_LOGE("%{public}s Create surface or pixelMap fail, surface null: %{public}d, pixelMap null: %{public}d, "
            "Ops: %{public}s", __func__, surface == nullptr, pixelMap == nullptr, cmdList->GetOpsWithDesc().c_str());
        return;
    }
    Playback(surface, cmdList, true);
    // InvalidateSurfaceCache must be called when reusing DMA buffer, otherwise it will cause incorrect display.
    InvalidateSurfaceCache(pixelMap);
    Media::InitializationOptions opts;
    opts.useDMA = true;
    opts.alphaType = Media::AlphaType::IMAGE_ALPHA_TYPE_PREMUL;
    auto opPixelMap = Media::PixelMap::Create(*pixelMap.get(), opts);
    if (opPixelMap == nullptr) {
        RS_LOGE("%{public}s Clone pixelMap fail, %{public}s", __func__, cmdList->GetOpsWithDesc().c_str());
        return;
    }
    cmdList->ClearOp();
    AddPixelMapDrawOp(cmdList, std::move(opPixelMap), width, height);
}

void RSModifiersDraw::ConvertCmdList(const std::shared_ptr<Drawing::DrawCmdList>& cmdList)
{
    if (cmdList->IsEmpty()) {
        return;
    }
    auto width = cmdList->GetWidth();
    auto height = cmdList->GetHeight();
    auto opPixelMap = CreatePixelMap(width, height);
    if (opPixelMap == nullptr) {
        RS_LOGE("%{public}s Create pixelMap fail, Ops: %{public}s", __func__, cmdList->GetOpsWithDesc().c_str());
        return;
    }
    auto surface = CreateSurface(opPixelMap, width, height);
    if (surface == nullptr) {
        RS_LOGE("%{public}s Create surface fail, Ops: %{public}s", __func__, cmdList->GetOpsWithDesc().c_str());
        return;
    }
    Playback(surface, cmdList, false);
    cmdList->ClearOp();
    AddPixelMapDrawOp(cmdList, std::move(opPixelMap), width, height);
}

bool RSModifiersDraw::TargetCommad(const std::unique_ptr<RSCommand>& cmd)
{
    if (cmd->GetDrawCmdList()->GetHybridRenderType() == Drawing::DrawCmdList::HybridRenderType::NONE) {
        return false;
    }

    bool targetCmd = false;
    switch (cmd->GetType()) {
        case RSCommandType::RS_NODE:
            if (cmd->GetSubType() == OHOS::Rosen::RSNodeCommandType::UPDATE_MODIFIER_DRAW_CMD_LIST ||
                // ADD_MODIFIER may contains drawing content and cannot be ignored.
                cmd->GetSubType() == OHOS::Rosen::RSNodeCommandType::ADD_MODIFIER) {
                targetCmd = true;
            }
            break;
        case RSCommandType::CANVAS_NODE:
            if (cmd->GetSubType() == OHOS::Rosen::RSCanvasNodeCommandType::CANVAS_NODE_UPDATE_RECORDING) {
                targetCmd = true;
            }
            break;
        default:
            break;
    }
    return targetCmd;
}

std::unique_ptr<RSTransactionData>& RSModifiersDraw::ConvertTransaction(
    std::unique_ptr<RSTransactionData>& transactionData)
{
    if (transactionData == nullptr || transactionData->IsEmpty()) {
        return transactionData;
    }

    SetThreadQos(QOS::QosLevel::QOS_USER_INTERACTIVE);
    bool needWait = false;
    for (auto& [nodeId, followType, command] : transactionData->GetPayload()) {
        auto drawCmdList = command == nullptr ? nullptr : command->GetDrawCmdList();
        if (drawCmdList == nullptr) {
            continue;
        }
        if (!TargetCommad(command)) {
            continue;
        }

        auto hybridRenderType = drawCmdList->GetHybridRenderType();
        RS_OPTIONAL_TRACE_NAME_FMT("RSModifiersDraw hybridRenderType=%d, width=%d, height=%d, nodeId=%" PRId64,
            hybridRenderType, drawCmdList->GetWidth(), drawCmdList->GetHeight(), command->GetNodeId());
        switch (hybridRenderType) {
            case Drawing::DrawCmdList::HybridRenderType::CANVAS:
                ConvertCmdListForCanvas(drawCmdList, command->GetNodeId());
                break;
            case Drawing::DrawCmdList::HybridRenderType::TEXT:
            case Drawing::DrawCmdList::HybridRenderType::SVG:
            case Drawing::DrawCmdList::HybridRenderType::HMSYMBOL:
            default:
                ffrt::submit([cmdList = std::move(drawCmdList)]() {
                    ConvertCmdList(cmdList);
                    }, {}, {}, ffrt::task_attr().qos(ffrt::qos_user_interactive));
                needWait = true;
                break;
        }
    }
    if (needWait) {
        ffrt::wait();
    }
    return transactionData;
}

std::unique_ptr<Media::PixelMap> RSModifiersDraw::GetPixelMapByNodeId(NodeId nodeId, bool useDMA)
{
    std::lock_guard<std::mutex> lock(surfaceMutex_);
    auto it = surfaceMap_.find(nodeId);
    if (it == surfaceMap_.end() || it->second.second == nullptr) {
        RS_LOGE("%{public}s Get pixelMap from map null", __func__);
        return nullptr;
    }
    InvalidateSurfaceCache(it->second.second);
    Media::InitializationOptions opts;
    opts.useDMA = useDMA;
    opts.alphaType = Media::AlphaType::IMAGE_ALPHA_TYPE_PREMUL;
    auto pixelMap = Media::PixelMap::Create(*it->second.second.get(), opts);
    if (pixelMap == nullptr) {
        RS_LOGE("%{public}s Clone pixelMap fail", __func__);
        return nullptr;
    }
    return pixelMap;
}

void RSModifiersDraw::RemoveSurfaceByNodeId(NodeId nodeId, bool postTask)
{
    if (postTask) {
        RSModifiersDrawThread::Instance().PostTask([nodeId]() { RemoveSurfaceByNodeId(nodeId, false); });
        return;
    }
    std::lock_guard<std::mutex> lock(surfaceMutex_);
    auto it = surfaceMap_.find(nodeId);
    if (it != surfaceMap_.end()) {
        surfaceMap_.erase(it);
    }
}

bool RSModifiersDraw::ResetSurfaceByNodeId(int32_t width, int32_t height, NodeId nodeId, bool postTask)
{
    if (postTask) {
        RSModifiersDrawThread::Instance().PostTask(
            [width, height, nodeId]() { ResetSurfaceByNodeId(width, height, nodeId, false); });
        return true;
    }
    RemoveSurfaceByNodeId(nodeId);
    auto pixelMap = CreatePixelMap(width, height);
    if (pixelMap == nullptr) {
        RS_LOGE("%{public}s Create pixelMap fail, nodeId: %{public}" PRId64, __func__, nodeId);
        return false;
    }
    auto surface = CreateSurface(pixelMap, width, height);
    if (surface == nullptr) {
        RS_LOGE("%{public}s Create surface fail, nodeId: %{public}" PRId64, __func__, nodeId);
        return false;
    }
    std::lock_guard<std::mutex> lock(surfaceMutex_);
    surfaceMap_.emplace(nodeId, std::make_pair(surface, std::move(pixelMap)));
    return true;
}
} // namespace Rosen
} // namespace OHOS